# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: xguo

import coin.exchange.base.kr_rest.level_book as level_book_mdl
import coin.exchange.base.kr_rest.public_client_base as pubcb
import coin.exchange.kraken.kr_rest.native_public_client as npubc
from coin.exchange.kraken.kr_rest.constants import active_symbol_list
from coin.exchange.kraken.kr_rest.product import KrakenProduct, remove_xz_from_rest_native_symbol
from coin.exchange.base.kr_rest.rest_client_base import RestUpdate

from coin.proto.coin_market_query_pb2 import (
    ProductTicker,
    ExchangeTicker,
    ProductKlineElement,
    ProductKline,
)


class KrakenFeedParser(object):
  @staticmethod
  def parse_native_ticker(update_msg, product):
    """ sample response message
    {
      "error": [],
      "result": {
        "BCHEUR": {
          "a": [ "1149.300000", "1", "1.000" ],
          "b": [ "1146.200000", "1", "1.000" ],
          "c": [ "1148.000000", "0.04709169" ],
          "v": [ "850.58118566", "2701.93765121" ],
          "p": [ "1167.922436", "1184.378661" ],
          "t": [ 1044, 6765 ],
          "l": [ "1141.700000", "1141.700000" ],
          "h": [ "1194.900000", "1222.500000" ],
          "o": "1184.000000"
        }
      }
    }"""
    assert len(update_msg['error']) == 0
    ticker = None
    for native_symbol, ticker in update_msg['result'].items():
      if remove_xz_from_rest_native_symbol(native_symbol) == product.native_symbol:
        break
    assert ticker is not None, '%s ticker not found! %s' % (product, update_msg)
    return ProductTicker(ask=float(ticker['a'][0]),
                         bid=float(ticker['b'][0]),
                         last=float(ticker['c'][0]),
                         high=float(ticker['h'][1]),
                         low=float(ticker['l'][1]),
                         volume_24hr=float(ticker['v'][1]),
                         native_symbol=product.native_symbol,
                         symbol=product.symbol)

  @staticmethod
  def parse_exchange_ticker(update_msg):
    assert len(update_msg['error']) == 0
    product_ticker_list = []
    for native_symbol in update_msg['result']:
      product = KrakenProduct.FromStrNativeProductNothrow(native_symbol)
      if product is None:
        continue
      product_ticker_list.append(KrakenFeedParser.parse_native_ticker(update_msg, product))
    assert len(product_ticker_list) > 0
    return ExchangeTicker(exchange='Kraken', each_ticker=product_ticker_list)

  @staticmethod
  def parse_level_book(update_msg, product):
    """ sample response message
    {
      "error": [],
      "result": {
        "BCHEUR": {
          "asks": [
            [ "1107.000000", "5.390", 1517478837 ],
            [ "1108.000000", "1.108", 1517478840 ],
            [ "1108.100000", "0.630", 1517478837 ]
          ],
          "bids": [
            [ "1104.100000", "1.984", 1517478836 ],
            [ "1104.000000", "0.002", 1517478797 ],
            [ "1102.000000", "0.075", 1517478755 ]
          ]
        }
      }
    } """
    assert len(update_msg['error']) == 0
    level_book = level_book_mdl.LevelBook(product)

    for elem in update_msg['result'][product.native_symbol]['asks']:
      level_elem = level_book_mdl.LevelElement(float(elem[0]), float(elem[1]), 0)
      level_book.add_ask(level_elem)

    for elem in update_msg['result'][product.native_symbol]['bids']:
      level_elem = level_book_mdl.LevelElement(float(elem[0]), float(elem[1]), 0)
      level_book.add_bid(level_elem)
    level_book.finalize()
    return level_book

  @staticmethod
  def parse_native_kline(update_msg, product, kline_period, start_time, end_time):
    klines = list(update_msg['result'].values())[0]
    start_ts = start_time.timestamp()
    end_ts = end_time.timestamp()
    kline_group = []
    for kline in klines:
      kline_ts = kline[0]
      if kline_ts < start_ts or kline_ts >= end_ts:
        continue
      product_kline = ProductKlineElement(
          kline_timestamp=int(kline_ts * 1e+9),
          open=float(kline[1]),
          high=float(kline[2]),
          low=float(kline[3]),
          close=float(kline[4]),
          volume=float(kline[6]),
      )
      kline_group.append(product_kline)
    assert len(kline_group) > 0
    return ProductKline(
        symbol=product.symbol,
        native_symbol=product.native_symbol,
        exchange='Kraken',
        market_type='Spot',
        kline_period=str(kline_period),
        klines=kline_group,
    )


class KrakenPublicClient(pubcb.PublicClientBase):
  def __init__(self, timeout=10):
    self.npubc = npubc.KrakenNativePublicClient(timeout=timeout)

  def query_ticker_impl(self, product):
    symbol = product.native_symbol
    update = self.npubc.get_ticker_information(symbol)
    update.msg = KrakenFeedParser.parse_native_ticker(update.msg, product)
    return update

  def query_exchange_ticker_impl(self):
    pair_list = ','.join(active_symbol_list)
    update = self.npubc.get_ticker_information(pair_list)
    update.msg = KrakenFeedParser.parse_exchange_ticker(update.msg)
    return update

  def query_level_book_impl(self, product):
    symbol = product.native_symbol
    update = self.npubc.get_order_book(symbol)
    update.msg = KrakenFeedParser.parse_level_book(update.msg, product)
    return update

  def query_history_kline_impl(self, product, kline_period, start_time, end_time):
    symbol = product.native_symbol
    if start_time:
      since = int(start_time.timestamp() - 3600)
    update = self.npubc.get_native_kline(symbol, kline_period, since=since)
    update.msg = KrakenFeedParser.parse_native_kline(update.msg, product, kline_period, start_time, end_time)
    return update
